package com.hmdp.utils;

import cn.hutool.core.thread.lock.LockUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.hmdp.constants.RedisConstants;
import io.netty.util.internal.StringUtil;
import org.springframework.asm.TypeReference;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.security.Key;
import java.time.LocalDateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import static com.hmdp.constants.RedisConstants.CACHE_NULL_TTL;

@Component
public class RedisCacheUtils {

    @Resource
    private LockUtils lockUtils;
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 设置会消失的缓存
     */
    public <V> void setWithTTL(String key, V value, Long time, TimeUnit timeUnit) {
        if (value == null) {
            stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, timeUnit);
        }
        stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(value), time, timeUnit);

    }

    /**
     * 设置带有 expire 的缓存
     */
    public <V> void setWithExpire(String key, V value, Long time, TimeUnit timeUnit) {
        RedisData json = RedisData.builder()
                .expireTime(LocalDateTime.now().plusSeconds(timeUnit.toSeconds(time)))
                .data(value)
                .build();
        stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(json));
    }
    /**
     * 非热点数据，阻塞进程，有较高一致性
     */
    public <R, ID> R queryWithTTL(String key, ID id, Class<R> type, Function<ID, R> function, Long time, TimeUnit timeUnit) throws InterruptedException {
        String redisKey = key + id;
        while (true) {
            // 查缓存
            String jsonStr = stringRedisTemplate.opsForValue().get(redisKey);

            // 有缓存，直接返回
            if (jsonStr != null) {
                return JSON.parseObject(jsonStr, type);
            }

            // 没有缓存，需要建立缓存
            // 1.获取到锁，执行缓存创建
            boolean lock = lockUtils.tryLock(key, id, "1");
            if (lock) break;

            // 2.未获取到锁，执行阻塞等待
            Thread.sleep(50);
        }
        // 执行持锁后操作
        // 1.先检查 redis缓存 是否已经存在
        String jsonStr = stringRedisTemplate.opsForValue().get(redisKey);
        if (jsonStr != null) {
            lockUtils.removeLock(key, id);
            return JSON.parseObject(jsonStr, type);
        }

        // 2.查数据库
        R json = function.apply(id);

        // 3.写入 redis缓存
        setWithTTL(redisKey, json, time, timeUnit);

        // 4.释放锁资源
        lockUtils.removeLock(key, id);
        return json;
    }

    // 线程池
    private static final ExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(10);

    /**
     * 热点数据，无较高的一致性要求：（获得锁的线程异步处理缓存重建，所有进程不阻塞都返回当前值）
     */
    public <R, ID> R queryWithExpire(String key, ID id, Class<R> type, Function<ID, R> function, Long time, TimeUnit timeUnit) {
        String redisKey = key + id;

        // 查缓存
        String jsonStr = stringRedisTemplate.opsForValue().get(redisKey);

        // 无缓存，当前数据库也无数据，直接返回空，解决了缓存穿透
        if (jsonStr == null || jsonStr.isEmpty()) {
            return null;
        }

        // 有缓存，判断是否过期
        RedisData redisData = JSONUtil.toBean(jsonStr, RedisData.class);
        LocalDateTime now = LocalDateTime.now();
        R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);

        // 未过期直接返回
        if (redisData.getExpireTime().isAfter(now)) {
            return r;
        }

        // 过期尝试获取锁
        boolean lock = lockUtils.tryLock(key, id, "1");
        if (lock) {
            EXECUTOR_SERVICE.submit(() -> {
                R json = function.apply(id);
                setWithExpire(redisKey, json, time, timeUnit);
            });
        }

        return r;
    }
}
